package com.yifeng.repo.micro.service.server.engine.dal.tx;

import com.yifeng.repo.micro.service.server.engine.configure.ForceTransactionProperties;
import com.yifeng.repo.micro.service.server.engine.dal.TransactionTargetManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.SpringTransactionAnnotationParser;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.PatternMatchUtils;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by daibing on 2024/1/27.
 */
@Slf4j
public class ForceTargetSpringTransactionAnnotationParser extends SpringTransactionAnnotationParser {
    private final String[] excludeClassNamePatterns;
    private final Set<String> writeMethodPrefixList;

    public ForceTargetSpringTransactionAnnotationParser(ForceTransactionProperties properties) {
        this.excludeClassNamePatterns = Optional.ofNullable(properties.getExcludeClassNamePrefixList()).orElse(Collections.emptyList()).toArray(new String[0]);
        this.writeMethodPrefixList = new HashSet<>(properties.getWriteMethodPrefixList());
        this.writeMethodPrefixList.addAll(Optional.ofNullable(properties.getExtraWriteMethodPrefixList()).orElse(Collections.emptyList()));
    }

    @Override
    public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
        AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
                element, Transactional.class, false, false);
        // 带 @Transactional 的方法优先级高于强事务管理
        if (attributes != null) {
            return checkAnnotateTransactionAttribute(parseTransactionAnnotation(attributes), element);
        }
        // 元素是类直接退出：仅拦截方法
        if (element instanceof Class) {
            return null;
        }
        // 不带 @Service 的类直接退出：仅拦截方法归属类带@Service的方法
        Class<?> clazz = ((Method) element).getDeclaringClass();
        if (!clazz.isAnnotationPresent(Service.class)) {
            return null;
        }
        // 支持前缀匹配排除：类似配置 com.yifeng.base.sms.server.boss.manager.UserManager.getUser
        String methodName = ((Method) element).getName();
        String target = clazz.getName() + "." + methodName;
        if (PatternMatchUtils.simpleMatch(excludeClassNamePatterns, target)) {
            return null;
        }
        // 支持前缀匹配写事务
        boolean write = writeMethodPrefixList.stream().anyMatch(methodName::startsWith);
        return checkForceTransactionAttribute(parseCustomTransaction(!write), target);
    }

    /**
     * 支持@Transactional在类上和方法上的场景
     */
    private TransactionAttribute checkAnnotateTransactionAttribute(TransactionAttribute transactionAttribute, AnnotatedElement element) {
        Set<String> targets;
        String entry;
        if (element instanceof Class) {
            targets = Arrays.stream(((Class<?>) element).getDeclaredMethods())
                    .filter(m -> Modifier.isPublic(m.getModifiers()))
                    .map(m -> ((Class<?>) element).getName() + "." + m.getName())
                    .collect(Collectors.toSet());
            entry = "AnnotatedByClass";
        } else {
            Class<?> clazz = ((Method) element).getDeclaringClass();
            String methodName = ((Method) element).getName();
            targets = Collections.singleton(clazz.getName() + "." + methodName);
            entry = "AnnotatedByMethod";
        }
        if (transactionAttribute.isReadOnly()) {
            TransactionTargetManager.addReadMethod(targets, entry);
        } else {
            TransactionTargetManager.addWriteMethod(targets, entry);
        }
        return transactionAttribute;
    }

    /**
     * 支持强事务方式加入的方法
     */
    private TransactionAttribute checkForceTransactionAttribute(TransactionAttribute transactionAttribute, String target) {
        if (transactionAttribute.isReadOnly()) {
            TransactionTargetManager.addReadMethod(target, "Force");
        } else {
            TransactionTargetManager.addWriteMethod(target, "Force");
        }
        return transactionAttribute;
    }

    private TransactionAttribute parseCustomTransaction(boolean readOnly) {
        RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
        rbta.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        rbta.setIsolationLevel(Isolation.DEFAULT.value());
        rbta.setTimeout(TransactionDefinition.TIMEOUT_DEFAULT);
        if (readOnly) {
            rbta.setReadOnly(true);
        } else {
            rbta.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        }
        return rbta;
    }
}
